package com.varian.security.config;

import cn.hutool.crypto.asymmetric.RSA;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.varian.security.authorization.AuthorizeHttpRequestCustomizer;
import com.varian.security.authorization.DefaultAuthorizeHttpRequestCustomizer;
import com.varian.security.authorization.VarianAuthorizeHttpRequestCustomizer;
import com.varian.security.authorization.VarianOAuth2ResourceServerConfigurer;
import com.varian.security.constant.SecurityConstant;
import com.varian.security.handler.VarianAccessDeniedHandler;
import com.varian.security.handler.VarianAuthenticationEntryPoint;
import com.varian.security.properties.VarianSecurityProperties;
import com.varian.tool.contant.VarianConstant;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.*;

/**
 * @author ben
 * @since 2024/6/23
 */
@AutoConfiguration
@RequiredArgsConstructor
@EnableConfigurationProperties(VarianSecurityProperties.class)
public class DefaultSecurityFilterConfig {

    private static final Logger log = LoggerFactory.getLogger(DefaultSecurityFilterConfig.class);
    private final VarianSecurityProperties properties;

    @Bean
    @SuppressWarnings("deprecation")
    public PasswordEncoder passwordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }

    @Bean
    public AuthorizeHttpRequestCustomizer defaultAuthorizeHttpRequestCustomizer(@Qualifier("requestMappingHandlerMapping") RequestMappingHandlerMapping requestMappingHandlerMapping) {
        return new DefaultAuthorizeHttpRequestCustomizer(requestMappingHandlerMapping);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSA rsa = properties.getRsa();
        RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
        RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(VarianConstant.WARRIOR)
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
        jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
        jwsAlgs.addAll(JWSAlgorithm.Family.EC);
        jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA);
        ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
        JWSKeySelector<SecurityContext> jwsKeySelector = new JWSVerificationKeySelector<>(jwsAlgs, jwkSource);
        jwtProcessor.setJWSKeySelector(jwsKeySelector);
        // Override the default Nimbus claims set verifier as NimbusJwtDecoder handles it
        // instead
        jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
        });
        return new NimbusJwtDecoder(jwtProcessor);
    }

    @Bean
    @SneakyThrows
    @Order(SecurityConstant.DEFAULT_SECURITY_FILTER_ORDER)
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http,
            List<AuthorizeHttpRequestCustomizer> customizers,
            JwtDecoder jwtDecoder
    ) {
        VarianAuthorizeHttpRequestCustomizer authorizeHttpRequestCustomizer = new VarianAuthorizeHttpRequestCustomizer(customizers);
        VarianOAuth2ResourceServerConfigurer resourceServerConfigurer = new VarianOAuth2ResourceServerConfigurer(jwtDecoder);
        http.authorizeHttpRequests(authorizeHttpRequestCustomizer::customize)
                .headers(Customizer.withDefaults())
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(AbstractHttpConfigurer::disable)
                .oauth2ResourceServer(resourceServerConfigurer::customize)
                .exceptionHandling(e ->
                        e.defaultAccessDeniedHandlerFor(new VarianAccessDeniedHandler(), AntPathRequestMatcher.antMatcher("/**"))
                                .defaultAuthenticationEntryPointFor(new VarianAuthenticationEntryPoint(), AntPathRequestMatcher.antMatcher("/**")));
        return http.build();
    }
}
